/**
 *  BlueCove - Java library for Bluetooth
 *
 *  Java docs licensed under the Apache License, Version 2.0
 *  http://www.apache.org/licenses/LICENSE-2.0 
 *   (c) Copyright 2001, 2002 Motorola, Inc.  ALL RIGHTS RESERVED.
 *
 *  Licensed to the Apache Software Foundation (ASF) under one
 *  or more contributor license agreements.  See the NOTICE file
 *  distributed with this work for additional information
 *  regarding copyright ownership.  The ASF licenses this file
 *  to you under the Apache License, Version 2.0 (the
 *  "License"); you may not use this file except in compliance
 *  with the License.  You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing,
 *  software distributed under the License is distributed on an
 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 *  KIND, either express or implied.  See the License for the
 *  specific language governing permissions and limitations
 *  under the License.
 *  
 *  @version $Id$
 */
package javax.bluetooth;

import java.io.IOException;

import javax.microedition.io.Connection;

import com.intel.bluetooth.DebugLog;
import com.intel.bluetooth.RemoteDeviceHelper;
import com.intel.bluetooth.UtilsJavaSE;

/**
 * The <code>RemoteDevice</code> class represents a remote Bluetooth device.
 * It provides basic information about a remote device including the device's
 * Bluetooth address and its friendly name.
 * 
 */
public class RemoteDevice {

	/**
	 * A bluetooth hex address
	 */
	private String addressStr;

	/**
	 * A bluetooth address for internal use
	 */
	private long addressLong;

	/**
	 * Creates a Bluetooth device based upon its address. The Bluetooth address
	 * must be 12 hex characters long. Valid characters are 0-9, a-f, and A-F.
	 * There is no preceding "0x" in the string. For example, valid Bluetooth
	 * addresses include but are not limited to:<BR>
	 * <code>008037144297</code><BR>
	 * <code>00af8300cd0b</code><BR>
	 * <code>014bd91DA8FC</code>
	 * 
	 * @param address
	 *            the address of the Bluetooth device as a 12 character hex
	 *            string
	 * 
	 * @exception NullPointerException
	 *                if <code>address</code> is <code>null</code>
	 * 
	 * @exception IllegalArgumentException
	 *                if <code>address</code> is the address of the local
	 *                device or is not a valid Bluetooth address
	 */
	protected RemoteDevice(String address) {
		if (address == null) {
			throw new NullPointerException("address is null");
		}
		if (address.length() != 12) {
			throw new IllegalArgumentException("Malformed address: " + address + "; should be 12 characters");
		}
		if (address.startsWith("-")) {
			throw new IllegalArgumentException("Malformed address: " + address + "; can't be negative");
		}
		DebugLog.debug("new RemoteDevice", address);
		this.addressStr = RemoteDeviceHelper.formatBluetoothAddress(address);
		try {
			if (this.addressStr.equals(LocalDevice.getLocalDevice().getBluetoothAddress())) {
				throw new IllegalArgumentException("can't use the LocalDevice address.");
			}
		} catch (BluetoothStateException e) {
			throw (RuntimeException) UtilsJavaSE.initCause(new RuntimeException("Can't initialize bluetooth support"), e);
		}
		this.addressLong = RemoteDeviceHelper.getAddress(address);
	}

	/**
	 * Determines if this is a trusted device according to the BCC.
	 * 
	 * @return <code>true</code> if the device is a trusted device, otherwise
	 *         <code>false</code>
	 */
	public boolean isTrustedDevice() {
		return RemoteDeviceHelper.implIsTrustedDevice(this);
	}

	/**
	 * Returns the name of this device. The Bluetooth specification calls this
	 * name the "Bluetooth device name" or the "user-friendly name". This method
	 * will only contact the remote device if the name is not known or
	 * <code>alwaysAsk</code> is <code>true</code>.
	 * 
	 * @param alwaysAsk
	 *            if <code>true</code> then the device will be contacted for
	 *            its name, otherwise, if there exists a known name for this
	 *            device, the name will be returned without contacting the
	 *            remote device
	 * 
	 * @return the name of the device, or <code>null</code> if the Bluetooth
	 *         system does not support this feature; if the local device is able
	 *         to contact the remote device, the result will never be
	 *         <code>null</code>; if the remote device does not have a name
	 *         then an empty string will be returned
	 * 
	 * @exception IOException
	 *                if the remote device can not be contacted or the remote
	 *                device could not provide its name
	 */
	public String getFriendlyName(boolean alwaysAsk) throws IOException {
		return RemoteDeviceHelper.implGetFriendlyName(this, this.addressLong, alwaysAsk);
	}

	/**
	 * Retrieves the Bluetooth address of this device. The Bluetooth address
	 * will be 12 characters long. Valid characters are 0-9 and A-F. This method
	 * will never return <code>null</code>.
	 * 
	 * @return the Bluetooth address of the remote device
	 */
	public final String getBluetoothAddress() {
		return this.addressStr;
	}

	/**
	 * Determines if two <code>RemoteDevice</code>s are equal. Two devices
	 * are equal if they have the same Bluetooth device address.
	 * 
	 * @param obj
	 *            the object to compare to
	 * 
	 * @return <code>true</code> if both devices have the same Bluetooth
	 *         address; <code>false</code> if both devices do not have the
	 *         same address; <code>false</code> if <code>obj</code> is
	 *         <code>null</code>; <code>false</code> if <code>obj</code>
	 *         is not a <code>RemoteDevice</code>
	 */
	public boolean equals(Object obj) {
		return obj != null && obj instanceof RemoteDevice && ((RemoteDevice) obj).addressLong == addressLong;
	}

	/**
	 * Computes the hash code for this object. This method will return the same
	 * value when it is called multiple times on the same object.
	 * 
	 * @return the hash code for this object
	 */
	public int hashCode() {
		return new Long(addressLong).hashCode();
	}

	/**
	 * Retrieves the Bluetooth device that is at the other end of the Bluetooth
	 * Serial Port Profile connection, L2CAP connection, or OBEX over RFCOMM
	 * connection provided. This method will never return <code>null</code>.
	 * 
	 * @param conn
	 *            the Bluetooth Serial Port connection, L2CAP connection, or
	 *            OBEX over RFCOMM connection whose remote Bluetooth device is
	 *            needed
	 * 
	 * @return the remote device involved in the connection
	 * 
	 * @exception IllegalArgumentException
	 *                if <code>conn</code> is not a Bluetooth Serial Port
	 *                Profile connection, L2CAP connection, or OBEX over RFCOMM
	 *                connection; if <code>conn</code> is a
	 *                <code>L2CAPConnectionNotifier</code>,
	 *                <code>StreamConnectionNotifier</code>, or
	 *                <code>SessionNotifier</code>
	 * 
	 * @exception IOException
	 *                if the connection is closed
	 * 
	 * @exception NullPointerException
	 *                if <code>conn</code> is <code>null</code>
	 */
	public static RemoteDevice getRemoteDevice(Connection conn) throws IOException {
		return RemoteDeviceHelper.implGetRemoteDevice(conn);
	}

	/**
	 * Attempts to authenticate this <code>RemoteDevice</code>.
	 * Authentication is a means of verifying the identity of a remote device.
	 * Authentication involves a device-to-device challenge and response scheme
	 * that requires a 128-bit common secret link key derived from a PIN code
	 * shared by both devices. If either side's PIN code does not match, the
	 * authentication process fails and the method returns <code>false</code>.
	 * The method will also return <code>false</code> if authentication is
	 * incompatible with the current security settings of the local device
	 * established by the BCC, if the stack does not support authentication at
	 * all, or if the stack does not support authentication subsequent to
	 * connection establishment.
	 * 
	 * <p>
	 * If this <code>RemoteDevice</code> has previously been authenticated,
	 * then this method returns <code>true</code> without attempting to
	 * re-authenticate this <code>RemoteDevice</code>.
	 * 
	 * @return <code>true</code> if authentication is successful; otherwise
	 *         <code>false</code>
	 * 
	 * @exception IOException
	 *                if there are no open connections between the local device
	 *                and this <code>RemoteDevice</code>
	 */
	public boolean authenticate() throws IOException {
		return RemoteDeviceHelper.authenticate(this);
	}

	/**
	 * Determines if this <code>RemoteDevice</code> should be allowed to
	 * continue to access the local service provided by the
	 * <code>Connection</code>. In Bluetooth, authorization is defined as the
	 * process of deciding if device X is allowed to access service Y. The
	 * implementation of the <code>authorize(Connection conn)</code> method
	 * asks the Bluetooth Control Center (BCC) to decide if it is acceptable for
	 * <code>RemoteDevice</code> to continue to access a local service over
	 * the connection <code>conn</code>. In devices with a user interface,
	 * the BCC is expected to consult with the user to obtain approval.
	 * 
	 * <p>
	 * Some Bluetooth systems may allow the user to permanently authorize a
	 * remote device for all local services. When a device is authorized in this
	 * way, it is known as a "trusted device" -- see
	 * {@link #isTrustedDevice() isTrustedDevice()}.
	 * 
	 * <p>
	 * The <code>authorize()</code> method will also check that the identity
	 * of the <code>RemoteDevice</code> can be verified through
	 * authentication. If this <code>RemoteDevice</code> has been authorized
	 * for <code>conn</code> previously, then this method returns
	 * <code>true</code> without attempting to re-authorize this
	 * <code>RemoteDevice</code>.
	 * 
	 * @see #isTrustedDevice
	 * 
	 * @param conn
	 *            the connection that this <code>RemoteDevice</code> is using
	 *            to access a local service
	 * 
	 * @return <code>true</code> if this <code>RemoteDevice</code> is
	 *         successfully authenticated and authorized, otherwise
	 *         <code>false</code> if authentication or authorization fails
	 * 
	 * @exception IllegalArgumentException
	 *                if <code>conn</code> is not a connection to this
	 *                <code>RemoteDevice</code>, or if the local device
	 *                initiated the connection, i.e., the local device is the
	 *                client rather than the server. This exception is also
	 *                thrown if <code>conn</code> was created by
	 *                <code>RemoteDevice</code> using a scheme other than
	 *                <code>btspp</code>, <code>btl2cap</code>, or
	 *                <code>btgoep</code>. This exception is thrown if
	 *                <code>conn</code> is a notifier used by a server to wait
	 *                for a client connection, since the notifier is not a
	 *                connection to this <code>RemoteDevice</code>.
	 * 
	 * @exception IOException
	 *                if <code>conn</code> is closed
	 */
	public boolean authorize(javax.microedition.io.Connection conn) throws IOException {
		return RemoteDeviceHelper.implAuthorize(this, conn);
	}

	/**
	 * Attempts to turn encryption on or off for an existing connection. In the
	 * case where the parameter <code>on</code> is <code>true</code>, this
	 * method will first authenticate this <code>RemoteDevice</code> if it has
	 * not already been authenticated. Then it will attempt to turn on
	 * encryption. If the connection is already encrypted then this method
	 * returns <code>true</code>. Otherwise, when the parameter
	 * <code>on</code> is <code>true</code>, either:
	 * <UL>
	 * <LI> the method succeeds in turning on encryption for the connection and
	 * returns <code>true</code>, or
	 * <LI> the method was unsuccessful in turning on encryption and returns
	 * <code>false</code>. This could happen because the stack does not
	 * support encryption or because encryption conflicts with the user's
	 * security settings for the device.
	 * </UL>
	 * 
	 * <p>
	 * In the case where the parameter <code>on</code> is <code>false</code>,
	 * there are again two possible outcomes:
	 * <UL>
	 * <LI> encryption is turned off on the connection and <code>true</code>
	 * is returned, or
	 * <LI> encryption is left on for the connection and <code>false</code> is
	 * returned.
	 * </UL>
	 * Encryption may be left on following <code>encrypt(conn,
	 * false)</code>
	 * for a variety of reasons. The user's current security settings for the
	 * device may require encryption or the stack may not have a mechanism to
	 * turn off encryption. Also, the BCC may have determined that encryption
	 * will be kept on for the physical link to this <code>RemoteDevice</code>.
	 * The details of the BCC are implementation dependent, but encryption might
	 * be left on because other connections to the same device need encryption.
	 * (All of the connections over the same physical link must be encrypted if
	 * any of them are encrypted.)
	 * 
	 * <p>
	 * While attempting to turn encryption off may not succeed immediately
	 * because other connections need encryption on, there may be a delayed
	 * effect. At some point, all of the connections over this physical link
	 * needing encryption could be closed or also have had the method
	 * <code>encrypt(conn, false)</code> invoked for them. In this case, the
	 * BCC may turn off encryption for all connections over this physical link.
	 * (The policy used by the BCC is implementation dependent.) It is
	 * recommended that applications do <code>encrypt(conn,
	 * false)</code> once
	 * they no longer need encryption to allow the BCC to determine if it can
	 * reduce the overhead on connections to this <code>RemoteDevice</code>.
	 * 
	 * <p>
	 * The fact that <code>encrypt(conn, false)</code> may not succeed in
	 * turning off encryption has very few consequences for applications. The
	 * stack handles encryption and decryption, so the application does not have
	 * to do anything different depending on whether the connection is still
	 * encrypted or not.
	 * 
	 * @param conn
	 *            the connection whose need for encryption has changed
	 * 
	 * @param on
	 *            <code>true</code> attempts to turn on encryption;
	 *            <code>false</code> attempts to turn off encryption
	 * 
	 * @return <code>true</code> if the change succeeded, otherwise
	 *         <code>false</code> if it failed
	 * 
	 * @exception IOException
	 *                if <code>conn</code> is closed
	 * 
	 * @exception IllegalArgumentException
	 *                if <code>conn</code> is not a connection to this
	 *                <code>RemoteDevice</code>; if <code>conn</code> was
	 *                created by the client side of the connection using a
	 *                scheme other than <code>btspp</code>,
	 *                <code>btl2cap</code>, or <code>btgoep</code> (for
	 *                example, this exception will be thrown if
	 *                <code>conn</code> was created using the
	 *                <code>file</code> or <code>http</code> schemes.); if
	 *                <code>conn</code> is a notifier used by a server to wait
	 *                for a client connection, since the notifier is not a
	 *                connection to this <code>RemoteDevice</code>
	 */
	public boolean encrypt(javax.microedition.io.Connection conn, boolean on) throws IOException {
		return RemoteDeviceHelper.implEncrypt(this, conn, on);
	}

	/**
	 * Determines if this <code>RemoteDevice</code> has been authenticated.
	 * <P>
	 * A device may have been authenticated by this application or another
	 * application. Authentication applies to an ACL link between devices and
	 * not on a specific L2CAP, RFCOMM, or OBEX connection. Therefore, if
	 * <code>authenticate()</code> is performed when an L2CAP connection is
	 * made to device A, then <code>isAuthenticated()</code> may return
	 * <code>true</code> when tested as part of making an RFCOMM connection to
	 * device A.
	 * 
	 * @return <code>true</code> if this <code>RemoteDevice</code> has
	 *         previously been authenticated; <code>false</code> if it has not
	 *         been authenticated or there are no open connections between the
	 *         local device and this <code>RemoteDevice</code>
	 */
	public boolean isAuthenticated() {
		return RemoteDeviceHelper.implIsAuthenticated(this);
	}

	/**
	 * Determines if this <code>RemoteDevice</code> has been authorized
	 * previously by the BCC of the local device to exchange data related to the
	 * service associated with the connection. Both clients and servers can call
	 * this method. However, for clients this method returns <code>false</code>
	 * for all legal values of the <code>conn</code> argument.
	 * 
	 * @param conn
	 *            a connection that this <code>RemoteDevice</code> is using to
	 *            access a service or provide a service
	 * 
	 * @return <code>true</code> if <code>conn</code> is a server-side
	 *         connection and this <code>RemoteDevice</code> has been
	 *         authorized; <code>false</code> if <code>conn</code> is a
	 *         client-side connection, or a server-side connection that has not
	 *         been authorized
	 * 
	 * @exception IllegalArgumentException
	 *                if <code>conn</code> is not a connection to this
	 *                <code>RemoteDevice</code>; if <code>conn</code> was
	 *                not created using one of the schemes <code>btspp</code>,
	 *                <code>btl2cap</code>, or <code>btgoep</code>; or if
	 *                <code>conn</code> is a notifier used by a server to wait
	 *                for a client connection, since the notifier is not a
	 *                connection to this <code>RemoteDevice</code>.
	 * 
	 * @exception IOException
	 *                if <code>conn</code> is closed
	 */
	public boolean isAuthorized(javax.microedition.io.Connection conn) throws IOException {
		return RemoteDeviceHelper.implIsAuthorized(this, conn);
	}

	/**
	 * Determines if data exchanges with this <code>RemoteDevice</code> are
	 * currently being encrypted.
	 * <P>
	 * Encryption may have been previously turned on by this or another
	 * application. Encryption applies to an ACL link between devices and not on
	 * a specific L2CAP, RFCOMM, or OBEX connection. Therefore, if
	 * <code>encrypt()</code> is performed with the <code>on</code>
	 * parameter set to <code>true</code> when an L2CAP connection is made to
	 * device A, then <code>isEncrypted()</code> may return <code>true</code>
	 * when tested as part of making an RFCOMM connection to device A.
	 * 
	 * @return <code>true</code> if data exchanges with this
	 *         <code>RemoteDevice</code> are being encrypted;
	 *         <code>false</code> if they are not being encrypted, or there
	 *         are no open connections between the local device and this
	 *         <code>RemoteDevice</code>
	 */
	public boolean isEncrypted() {
		return RemoteDeviceHelper.implIsEncrypted(this);
	}

}
